package com.github.eden.generator;

import com.github.eden.config.Config;
import com.github.eden.db.DbColumn;
import com.github.eden.db.DbConnection;
import com.github.eden.db.DbTableParser;
import com.github.eden.db.DbTable;
import com.github.eden.util.Utils;
import freemarker.template.TemplateException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.time.DateFormatUtils;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;

@Slf4j
public class CodeGenerator {

    private Config config;
    private DbConnection connection;
    private TemplateEngine engine;
    private DbTableParser tableParser;

    public CodeGenerator(Config config) throws IOException {
        this.config = config;
        connection = new DbConnection(config);
        tableParser = new DbTableParser(config);
        engine = new TemplateEngine("/template");
    }

    public void execute() throws IOException, TemplateException {
        log.info("Code generating...");
        String projectPath = Utils.getRootPath() + "project/" + config.getProjectName() + "-" + DateFormatUtils.format(new Date(),
                "yyyyMMdd-HHmmss");
        String javaPath = projectPath + "/src/main/java/";
        String resourcePath = projectPath + "/src/main/resources/";
        String basePackage = config.getBasePackage();
        String basePackagePath = javaPath + Utils.packageToPath(config.getBasePackage());

        // build param
        Map<String, Object> param = new HashMap<>();
        param.put("basePackage", basePackage);
        param.put("projectName", config.getProjectName());
        param.put("date", DateFormatUtils.format(new Date(), "yyyy-MM-dd"));

        List<DbTable> tables = connection.getTables();
        // table和字段的规则解析
        tableParser.parse(tables);

        for (DbTable table : tables) {
            String tableName = table.getTableName();
            if (CollectionUtils.isNotEmpty(config.getUnusedTables()) && config.getUnusedTables().contains(tableName)) {
                continue;
            }

            // build param
            Map<String, Object> tableParam = new HashMap<>(param);
            tableParam.put("table", table);

            List<DbColumn> columns = table.getColumns();
            for (DbColumn column : columns) {
                String fieldName = Utils.toCamelCase(column.getColumnName());
                column.setFieldName(fieldName);
                column.setIsCreateTimeColumn(column.getColumnName().equals(config.getCreateTimeColumn()));
                column.setIsUpdateTimeColumn(column.getColumnName().equals(config.getUpdateTimeColumn()));
                column.setIsDeletedColumn(column.getColumnName().equals(config.getDeletedColumn()));
            }

            if (!table.getIsRelationalTable()) {
                // Controller
                String controllerFile = basePackagePath + "controller/" + table.getClassName() + "Controller.java";
                String controller = engine.render("backend/Controller.java.ftl", tableParam);
                FileUtils.write(new File(controllerFile), controller, StandardCharsets.UTF_8);

                // Service
                String serviceFile = basePackagePath + "service/" + table.getClassName() + "Service.java";
                String service = engine.render("backend/Service.java.ftl", tableParam);
                FileUtils.write(new File(serviceFile), service, StandardCharsets.UTF_8);
            }

            // Dao
            String daoFile = basePackagePath + "dao/" + table.getClassName() + "Dao.java";
            String dao = engine.render("backend/Dao.java.ftl", tableParam);
            FileUtils.write(new File(daoFile), dao, StandardCharsets.UTF_8);

            // Entity
            String entityFile = basePackagePath + "entity/" + table.getClassName() + ".java";
            String entity = engine.render("backend/Entity.java.ftl", tableParam);
            FileUtils.write(new File(entityFile), entity, StandardCharsets.UTF_8);

            // Mapper
            String mapperFile = resourcePath + "mapper/" + table.getClassName() + "Mapper.xml";
            String mapper = engine.render("backend/Mapper.xml.ftl", tableParam);
            FileUtils.write(new File(mapperFile), mapper, StandardCharsets.UTF_8);

            if (!table.getIsRelationalTable()) {
                // UI
                String viewFile = projectPath + "/webui/src/views/" + table.getClassName() + ".vue";
                String view;
                if ("light".equals(config.getTheme())) {
                    view = engine.render("frontend/ViewLight.vue.ftl", tableParam);
                } else {
                    view = engine.render("frontend/View.vue.ftl", tableParam);
                }
                FileUtils.write(new File(viewFile), view, StandardCharsets.UTF_8);
            }
        }

        // LoginController
        String controllerFile = basePackagePath + "controller/LoginController.java";
        String controller = engine.render("backend/LoginController.java.ftl", param);
        FileUtils.write(new File(controllerFile), controller, StandardCharsets.UTF_8);

        // BaseDao.java
        String baseDaoHandlerFile = basePackagePath + "common/" + "BaseDao.java";
        String baseDaoHandler = engine.render("backend/common/BaseDao.java.ftl", param);
        FileUtils.write(new File(baseDaoHandlerFile), baseDaoHandler, StandardCharsets.UTF_8);

        // QueryBuilder.java
        String queryBuilderFile = basePackagePath + "common/" + "QueryBuilder.java";
        String queryBuilder = engine.render("backend/common/QueryBuilder.java.ftl", param);
        FileUtils.write(new File(queryBuilderFile), queryBuilder, StandardCharsets.UTF_8);

        // QueryCondition.java
        String queryConditionFile = basePackagePath + "common/" + "QueryCondition.java";
        String queryCondition = engine.render("backend/common/QueryCondition.java.ftl", param);
        FileUtils.write(new File(queryConditionFile), queryCondition, StandardCharsets.UTF_8);

        // QueryParam.java
        String queryParamFile = basePackagePath + "common/" + "QueryParam.java";
        String queryParam = engine.render("backend/common/QueryParam.java.ftl", param);
        FileUtils.write(new File(queryParamFile), queryParam, StandardCharsets.UTF_8);

        // Page.java
        String pageFile = basePackagePath + "common/" + "Page.java";
        String page = engine.render("backend/common/Page.java.ftl", param);
        FileUtils.write(new File(pageFile), page, StandardCharsets.UTF_8);

        // Pageable.java
        String pageableFile = basePackagePath + "common/" + "Pageable.java";
        String pageable = engine.render("backend/common/Pageable.java.ftl", param);
        FileUtils.write(new File(pageableFile), pageable, StandardCharsets.UTF_8);

        // PagePlugin.java
        String pagePluginFile = basePackagePath + "common/" + "PagePlugin.java";
        String pagePlugin = engine.render("backend/common/PagePlugin.java.ftl", param);
        FileUtils.write(new File(pagePluginFile), pagePlugin, StandardCharsets.UTF_8);

        // WebResult.java
        String resultFile = basePackagePath + "common/" + "WebResult.java";
        String result = engine.render("backend/common/WebResult.java.ftl", param);
        FileUtils.write(new File(resultFile), result, StandardCharsets.UTF_8);

        // WebErrorCode.java
        String errorCodeFile = basePackagePath + "common/" + "WebErrorCode.java";
        String errorCode = engine.render("backend/common/WebErrorCode.java.ftl", param);
        FileUtils.write(new File(errorCodeFile), errorCode, StandardCharsets.UTF_8);

        // WebExceptionHandler.java
        String exceptionHandlerFile = basePackagePath + "common/" + "WebExceptionHandler.java";
        String exceptionHandler = engine.render("backend/common/WebExceptionHandler.java.ftl", param);
        FileUtils.write(new File(exceptionHandlerFile), exceptionHandler, StandardCharsets.UTF_8);

        // ServiceException.java
        String serviceExceptionFile = basePackagePath + "common/" + "ServiceException.java";
        String serviceException = engine.render("backend/common/ServiceException.java.ftl", param);
        FileUtils.write(new File(serviceExceptionFile), serviceException, StandardCharsets.UTF_8);

        // SpringConfig.java
        String springConfigFile = basePackagePath + "spring/" + "SpringConfig.java";
        String springConfig = engine.render("backend/spring/SpringConfig.java.ftl", param);
        FileUtils.write(new File(springConfigFile), springConfig, StandardCharsets.UTF_8);

        // Application.java
        String applicationFile = basePackagePath + "Application.java";
        String application = engine.render("backend/Application.java.ftl", param);
        FileUtils.write(new File(applicationFile), application, StandardCharsets.UTF_8);

        // application.properties
        String applicationPropFile = projectPath + "/config/application.properties";
        String applicationProp = engine.render("backend/application.properties.ftl", param);
        FileUtils.write(new File(applicationPropFile), applicationProp, StandardCharsets.UTF_8);

        // application-dev.properties
        param.put("applicationMain", basePackage + ".Application");
        param.put("dbDriverClass", config.getDbDriverClass());
        param.put("dbUrl", config.getDbUrl());
        param.put("dbUsername", config.getDbUsername());
        param.put("dbPassword", config.getDbPassword());
        String applicationDevProp = engine.render("backend/application-dev.properties.ftl", param);
        String applicationDevPropFile = projectPath + "/config/application-dev.properties";
        FileUtils.write(new File(applicationDevPropFile), applicationDevProp, StandardCharsets.UTF_8);

        // application-prod.properties
        String applicationProdPropFile = projectPath + "/config/application-prod.properties";
        String applicationProdProp = engine.render("backend/application-prod.properties.ftl", param);
        FileUtils.write(new File(applicationProdPropFile), applicationProdProp, StandardCharsets.UTF_8);

        // assembly.xml
        String assemblyFile = projectPath + "/assembly.xml";
        String assembly = engine.render("backend/assembly.xml.ftl", param);
        FileUtils.write(new File(assemblyFile), assembly, StandardCharsets.UTF_8);

        // pom.xml
        String pomFile = projectPath + "/pom.xml";
        String pom = engine.render("backend/pom.xml.ftl", param);
        FileUtils.write(new File(pomFile), pom, StandardCharsets.UTF_8);

        // start.sh
        String startShFile = projectPath + "/bin/start.sh";
        String startSh = engine.render("backend/start.sh.ftl", param);
        FileUtils.write(new File(startShFile), startSh, StandardCharsets.UTF_8);

        // webui
        Utils.copyResources("webui/", projectPath);

        Map<String, Object> webuiParams = new HashMap<>();
        webuiParams.put("tables", tables);

        // webapi
        String webapiFile = projectPath + "/webui/src/webapi/WebApi.js";
        String webapi = engine.render("frontend/WebApi.js.ftl", webuiParams);
        FileUtils.write(new File(webapiFile), webapi, StandardCharsets.UTF_8);
        
        // ui routes
        String routesFile = projectPath + "/webui/src/router/routes.js";
        String routes = engine.render("frontend/routes.js.ftl", webuiParams);
        FileUtils.write(new File(routesFile), routes, StandardCharsets.UTF_8);

        Map<String, Object> layoutUiParams = new HashMap<>();
        layoutUiParams.put("systemName", config.getSystemName());
        // ui Login.vue
        String loginUiFile = projectPath + "/webui/src/views/Login.vue";
        String loginUi = engine.render("frontend/Login.vue.ftl", layoutUiParams);
        FileUtils.write(new File(loginUiFile), loginUi, StandardCharsets.UTF_8);

        // ui Layout.vue
        if ("light".equals(config.getTheme())) {
            String layoutLightUiFile = projectPath + "/webui/src/views/Layout.vue";
            String layoutLightUi = engine.render("frontend/LayoutLight.vue.ftl", layoutUiParams);
            FileUtils.write(new File(layoutLightUiFile), layoutLightUi, StandardCharsets.UTF_8);
        } else {
            String layoutUiFile = projectPath + "/webui/src/views/Layout.vue";
            String layoutUi = engine.render("frontend/Layout.vue.ftl", layoutUiParams);
            FileUtils.write(new File(layoutUiFile), layoutUi, StandardCharsets.UTF_8);
        }

        // ui index.html
        String indexUiFile = projectPath + "/webui/public/index.html";
        String indexUi = engine.render("frontend/index.html.ftl", layoutUiParams);
        FileUtils.write(new File(indexUiFile), indexUi, StandardCharsets.UTF_8);

        log.info("Code generated.");
    }

}
